/**
 * 
 */
package org.rollinitiative.d20.entity;

import java.util.EnumSet;
import java.util.Set;

import javax.xml.namespace.QName;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.rollinitiative.d20.Adjustment;
import org.rollinitiative.d20.Adjustment.AdjustmentSource;
import org.rollinitiative.d20.entity.classes.CharacterClass;
import org.rollinitiative.d20.entity.classes.ClassLevel;
import org.rollinitiative.d20.entity.races.Race;
import org.rollinitiative.d20.entity.talents.Skill;
import org.rollinitiative.d20.entity.talents.SkillTraining;
import org.rollinitiative.d20.persist.BridgeException;

/**
 * @author bebopjmm
 * 
 */
public class NonPlayerFactory extends ActorFactory
{
   static final Log LOG = LogFactory.getLog(NonPlayerFactory.class);


   /**
    * The method constructs a new NonPlayer instance from the specified tempate.
    * 
    * @param npcTemplateID id of the NPC template to base new NonPlayer from
    * @return
    * @throws InvalidEntityException
    */
   public void initNPC(NonPlayer npc) throws InvalidEntityException, BridgeException
   {
      if (!verifyBridgeConfiguration()) {
         throw new BridgeException("Not all bridges have been assigned");
      }

      // ------------------------------------------
      // Overview of instantiation steps.
      // 1. Configure the abilities using the template
      // 2. Initialize the baseline set of skills
      // 3. Configure racial baseline info using the template
      // 4. Process the advancements by level
      // ------------------------------------------

      // 1. Configuring the abilities using the ncpTemplate
      npc.initAbilities();
      if (LOG.isDebugEnabled()) {
         for (Ability a : EnumSet.range(Ability.STR, Ability.CHA)) {
            LOG.debug("Ability, " + a + " has base value: " + npc.abilities.get(a).getBase());
         }
      }

      // 2. Establish the base set of skills (Everyone has untrained skills in their list)
      Skill[] untrainedSkills = talentBridge.retrieveUntrainedSkills();
      if (untrainedSkills != null) {
         LOG.info("Initializing basic skills, total = " + untrainedSkills.length);
         for (Skill skill : untrainedSkills) {
            npc.initSkill(skill);
         }
      }
      else {
         LOG.warn("NULL untrained skills! There is likely a problem with the SkillContentBridge!");
      }

      // 3. Configure Racial Baseline info
      QName raceID = npc.getRaceID();
      Race npcRace = raceBridge.retrieveRace(raceID);
      npc.setRace(npcRace);
      if (LOG.isDebugEnabled()) {
         LOG.debug("Abilities following racial assignment:");
         for (Ability a : EnumSet.range(Ability.STR, Ability.CHA)) {
            LOG.debug("Ability, " + a + " has current value: " + npc.abilities.get(a).getCurrent());
         }
      }
      LOG.debug("racial characteristics complete");

      // 4. Process the Level Advancements. For each level we need to
      // 4.1. Advance the correct class level.
      // 4.2. Advance the hit points
      // 4.3. Advance the skill points
      // 4.4. Advance abilities
      CharacterLevel characterLevel;
      CharacterClass levelClass = null;
      LOG.info("Total levels for " + npc.getName() + " = " + npc.level);
      for (int i = 0; i < npc.level; i++) {
         characterLevel = npc.getCharacterLevels().levels.get(i);

         // Aggregate Hit Points.
         npc.levelUpHP(characterLevel.getHitPoints());

         // TODO Aggregate Skills
         for (SkillTraining training : characterLevel.skills) {
            Skill skill = talentBridge.retrieveSkill(training.getSkillID());

            // if new skill, then it must be initialized.
            if (!npc.hasSkill(skill)) {
               npc.initSkill(skill);
            }
            // add the ranks to the skill
            npc.advanceSkill(skill, training.getPoints());
         }
         
         // Apply Class Bonuses
         if(characterLevel.getClassID().getLocalPart().equals("racial")) {
            LOG.warn("NO SUPPORT YET FOR RACIAL CLASS LEVELS");
            continue;
            // TODO Add support for racial level bonuses (if necessary)
         }
         levelClass = classBridge.retrieveClass(characterLevel.getClassID());
         if (npc.classMap.containsKey(levelClass)) {
            int level = npc.classMap.get(levelClass).intValue() + 1;
            LOG.debug("Advancing " + npc.getName() + " level in "
                  + characterLevel.getClassID().getLocalPart() + " to " + level);
            npc.classMap.put(levelClass, level);
         }
         else {
            npc.classMap.put(levelClass, Integer.valueOf(1));
            LOG.debug("Initializing " + npc.getName() + " level in "
                  + characterLevel.getClassID().getLocalPart() + " to 1");
         }
      }

      // Establish Base Attack Bonus and Saving Throws based on class(es) and level.
      Set<CharacterClass> charClasses = npc.classMap.keySet();
      int bab = 0;
      int fort = 0;
      int will = 0;
      int refl = 0;
      int level = 0;
      ClassLevel classLevel;
      StringBuffer nameBuffer = new StringBuffer();
      LOG.debug("Total characterClasses to process = " + charClasses.size());
      for (CharacterClass characterClass : charClasses) {
         level = npc.classMap.get(characterClass).intValue();
         LOG.debug(npc.getName() + " is a level " + level + characterClass.getShortName());
         classLevel = characterClass.getAdvancementLevel(level-1);
         bab += classLevel.getBaseAttackBonus();
         fort += classLevel.getSaves().fort;
         will += classLevel.getSaves().will;
         refl += classLevel.getSaves().refl;
         nameBuffer.append(characterClass.getShortName() + level);
      }
//      npc.setClassDescript(nameBuffer.toString());
      npc.attackStats.modifyBaseAttackBonus(bab);
//      npc.setMaxBaseAttackBonus(bab);

      // Assign Class-Based Adjustments to Saving Throws
      Adjustment saveAdj;
      AbilityListenerValue saveVal;
      
      saveAdj = new Adjustment(AdjustmentSource.INHERENT, fort, new QName(
            "java:org.rollinitiative.d20.entity", "classLevel"));
      saveVal = npc.saves.get(SavingThrow.FORTITUDE);
      saveVal.addAdjustment(saveAdj);
      
      saveAdj = new Adjustment(AdjustmentSource.INHERENT, will, new QName(
            "java:org.rollinitiative.d20.entity", "classLevel"));
      saveVal = npc.saves.get(SavingThrow.WILL);
      saveVal.addAdjustment(saveAdj);
      
      saveAdj = new Adjustment(AdjustmentSource.INHERENT, refl, new QName(
            "java:org.rollinitiative.d20.entity", "classLevel"));
      saveVal = npc.saves.get(SavingThrow.REFLEX);
      saveVal.addAdjustment(saveAdj);

      // // Process the character's inventory.
      // InventoryItemType[] inventory = npcTemplate.getEquipment().getItemArray();
      // processInventory(npc, inventory);
   }

}
